<!doctype html>
<!--

  TODO:
  * Add comments to make this code more understandable.
  * Move createExpectedElement and showError to a .js file,
    and use it from here and from math.html.
  * Break up math.css into two files, one of which will be
    used here.
  * Change the "value" label the the UI to say "code".
  * Break up the output so that each instruction shows up
    in its own line.

-->
<html>
  <head>
    <title>ohm/PL/0 demo</title>
    <meta charset=utf-8>
    <link href="../math/math.css" rel="stylesheet">
    <script src="../lib.js"></script>
    <script type="text/ohm-js">

PL0 {

  Start
    = Seq

  Seq
    = Stmt Seq  -- seq
    | Stmt      -- base

  Stmt
    = var ident "=" Expr ";"       -- var
    | print AddExpr ";"            -- print
    | if Expr then Stmt else Stmt  -- if3
    | if Expr then Stmt            -- if2
    | ident ":=" Expr ";"          -- assign

  Expr
    = AddExpr

  AddExpr
    = AddExpr "+" MulExpr  -- plus
    | AddExpr "-" MulExpr  -- minus
    | MulExpr

  MulExpr
    = MulExpr "*" PriExpr  -- times
    | MulExpr "/" PriExpr  -- divide
    | PriExpr

  PriExpr
    = "(" Expr ")"  -- paren
    | "+" PriExpr   -- pos
    | "-" PriExpr   -- neg
    | number
    | ident

  number  (a number literal)
    = digit* "." digit+  -- fract
    | digit+             -- whole

  ident  (an identifier)
    = ~keyword letter alnum*

  var   = "var" ~alnum
  print = "print" ~alnum
  if    = "if" ~alnum
  then  = "then" ~alnum
  else  = "else" ~alnum

  keyword = var | print | if | then | else

}

    </script>
    <script src="../../dist/ohm.js"></script>
  </head>
  <body>
    <input type="text" id="input" placeholder="Enter a PL/0 expression..." size="80"></input>
    <div id="errorDiv">
      <div id="spaces"></div>
      <wrapperWrapper><wrapper>
        <div id="error"><label>Expected: </label><span id="errorDetails"></span></div>
      </wrapper></wrapperWrapper>
    </div>
    <div id="value"></div>
    <script>

var g = ohm.grammarFromScriptElement();

// ---------------------------------------------------------------------------------------------------------------------

var s = g.createSemantics();

/*
s.addInheritedAttribute('symbolTableIn', {
  _base: function(s) {
    symbolTableIn.set([]);
  },
  _nonterminal: function(child) {
    if (child === this.firstChild()) {
      symbolTableIn.set(symbolTableIn(this));
    } else {
      symbolTableIn.set(this.childBefore(child).symbolTableOut);
    }
  }
});
*/

s.addAttribute('symbolTableOut', {
  Start: function(q) {
    return q.symbolTableOut;
  },
  Stmt_var: function(_var, n, _eq, e, _sc) {
    return [].concat(symbolTableIn(this)).concat([n.sourceString]);
  },
  _nonterminal: function(children) {
    return this.hasNoChildren() ? symbolTableIn(this) : this.lastChild().symbolTableOut;
  },
  _terminal: function() {
    return symbolTableIn(this);
  }
});

s.addAttribute('codeOut', {
  Start: function(q) {
    return q.codeOut;
  },
  Seq_seq: function(s, q) {
    return [].concat(s.codeOut).concat(['pop']).concat(q.codeOut);
  },
  Seq_base: function(s) {
    return s.codeOut;
  },
  Stmt_var: function(_var, n, _eq, e, _sc) {
    return [].concat(e.codeOut)
             .concat(['pushLit(' + this.symbolTableOut.indexOf(n.sourceString) + ')', 'store']);
  },
  Stmt_print: function(_print, e, _sc) {
    return [].concat(e.codeOut).concat(['print']);
  },
  Stmt_assign: function(n, _eq, e, _sc) {
    return [].concat(e.codeOut(e))
             .concat(['pushLit(' + this.symbolTableOut.indexOf(n.sourceString) + ')', 'store']);
  },
  Stmt_if3: function(_if, c, _then, t, _else, f) {
    var tCode = t.codeOut;
    var fCode = f.codeOut;
    return [].concat(c.codeOut)
             .concat(['jmp0(' + (tCode.length + 1) + ')'])
             .concat(tCode)
             .concat(['jmp(' + fCode.length + ')'])
             .concat(fCode);
  },

  Stmt_if2: function(_if, c, _then, t) {
    var tCode = t.codeOut;
    return [].concat(c.codeOut)
             .concat(['jmp0(' + tCode.length + ')'])
             .concat(tCode);
  },
  AddExpr_plus: function(x, _, y) {
    return [].concat(x.codeOut).concat(y.codeOut).concat(['add']);
  },
  AddExpr_minus: function(x, _, y) {
    return [].concat(x.codeOut).concat(y.codeOut).concat(['sub']);
  },
  MulExpr_times: function(x, _, y) {
    return [].concat(x.codeOut).concat(y.codeOut).concat(['mul']);
  },
  MulExpr_divide: function(x, _, y) {
    return [].concat(x.codeOut).concat(y.codeOut).concat(['div']);
  },
  PriExpr_paren: function(_l, e, _r) {
    return e.codeOut;
  },
  PriExpr_pos: function(_, e) {
    return e.codeOut;
  },
  PriExpr_neg: function(_, e) {
    return [].concat([e.codeOut]).concat(['neg']);
  },
  ident: function(_l, _ns) {
    return ['pushReg(' + this.symbolTableOut.indexOf(this.sourceString) + ')'];
  },
  number: function(_) {
    return ['pushLit(' + parseFloat(this.sourceString) + ')'];
  }
});

// ---------------------------------------------------------------------------------------------------------------------

var input = document.getElementById('input');
var spaces = document.getElementById('spaces');
var error = document.getElementById('error');
var errorDetails = document.getElementById('errorDetails');
var errorDiv = document.getElementById('errorDiv');

input.value = '';
hideError();

input.oninput = function() {
  hideError();
  var m = g.match(this.value);
  if (m.failed()) {
    this.className = 'error';
    showError(m);
  } else {
    show('value', s(m).codeOut.join('\n'));
  }
};

function hideError() {
  errorDiv.className = errorDiv.className = 'hidden';
}

function showError(r) {
  setTimeout(function() {
    // Position the error bubble to line up with the offending input
    spaces.innerHTML = repeat(' ', r.getRightmostFailurePosition());

    // Set up the details, i.e., what input was expected at that position
    removeChildren(errorDetails);
    r.getRightmostFailures().forEach(function(failure, idx, failures) {
      var element = createExpectedElement(failure);
      if (idx > 0) {
        errorDetails.appendChild(makeElement('light', idx === failures.length - 1 ? ', or ' : ', '));
      }
      errorDetails.appendChild(element);
    });

    // Show error
    errorDiv.className = 'visible';
  }, 0);
}

var elt = makeElement;

function createExpectedElement(failure) {
  if (failure.isStringTerminal()) {
    return elt('literal',
      elt('light', '"'),
      elt('code', failure.getText()),
      elt('light', '"'));
  } else if (failure.isCode()) {
    return elt('code', failure.getText());
  } else {
    return elt('description', failure.getText());
  }
}

window.test = function(t) {
  var valueField = document.querySelector('#value');

  input.value = 'var x = 3; print x + 1;';
  input.oninput();
  var expected = 'pushLit(3) pushLit(0) store pop pushReg(0) pushLit(1) add print';
  var actual = valueField.textContent.split(/\s+/).join(' ');
  t.equal(actual, expected, 'simple program produces expected code');
  t.end();
};
    </script>
  </body>
</html>

